package com.company.cloud.product.modules.mgr.service.impl;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.redisson.RedissonMultiLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.company.cloud.common.asserts.ValidatorUtils;
import com.company.cloud.common.bean.LoginSimpleUserInfo;
import com.company.cloud.common.exception.BusinessException;
import com.company.cloud.common.utils.EnumUtils;
import com.company.cloud.product.constant.ProductErrorEnum;
import com.company.cloud.product.modules.api.constant.ProductStockBusiTypeEnum;
import com.company.cloud.product.modules.api.request.ProductStockMultipleSkuItem;
import com.company.cloud.product.modules.api.request.ProductStockMultipleSkuOpParam;
import com.company.cloud.product.modules.api.request.ProductStockSingleSkuOpParam;
import com.company.cloud.product.modules.api.response.Sku;
import com.company.cloud.product.modules.base.entity.ComeOrder;
import com.company.cloud.product.modules.base.entity.ComeOrderItem;
import com.company.cloud.product.modules.base.entity.OutOrder;
import com.company.cloud.product.modules.base.entity.OutOrderItem;
import com.company.cloud.product.modules.base.entity.ProductStockInfo;
import com.company.cloud.product.modules.base.service.ComeOrderItemService;
import com.company.cloud.product.modules.base.service.ComeOrderService;
import com.company.cloud.product.modules.base.service.OutOrderItemService;
import com.company.cloud.product.modules.base.service.OutOrderService;
import com.company.cloud.product.modules.base.service.ProductStockInfoService;
import com.company.cloud.product.modules.cache.service.ProductInfoCacheService;
import com.company.cloud.product.modules.mgr.controller.req.ProductStockComeSaveParam;
import com.company.cloud.product.modules.mgr.controller.req.ProductStockComeSaveParamItem;
import com.company.cloud.product.modules.mgr.controller.req.ProductStockOutSaveParam;
import com.company.cloud.product.modules.mgr.controller.req.ProductStockOutSaveParamItem;
import com.company.cloud.product.modules.mgr.service.ProductStockMgrService;
import com.company.cloud.product.modules.mgr.utils.ProductLockKeys;
import com.company.cloud.product.modules.mgr.utils.ProductSnUtils;

@Service
public class ProductStockMgrServiceImpl implements ProductStockMgrService{

	@Autowired
	private ProductStockInfoService productStockInfoService;
	@Autowired
	private ProductInfoCacheService productInfoCacheService;
	
	@Autowired
	private ComeOrderService comeOrderService;
	@Autowired
	private ComeOrderItemService comeOrderItemService;
	@Autowired
	private OutOrderService outOrderService;
	@Autowired
	private OutOrderItemService outOrderItemService;
	@Autowired
	private RedissonClient redissonClient;
	
	@Transactional
	@Override
	public void saveComeOrder(ProductStockComeSaveParam params, LoginSimpleUserInfo userInfo) {
		ValidatorUtils.validate(params);
		String custNo;
		if(StringUtils.isEmpty(params.getCustNo())) {
			custNo=userInfo.getCompanyNo();
		}else {
			custNo=params.getCustNo();
		}
		
		ProductStockMultipleSkuOpParam psmsoparams=new ProductStockMultipleSkuOpParam();
		psmsoparams.setCompanyNo(userInfo.getCompanyNo());
		psmsoparams.setCustNo(custNo);
		
		
		Map<String, Sku> skuMap=null;
		if(!CollectionUtils.isEmpty(params.getItems())) {
			Set<String> skuNos=new HashSet<String>();
			params.getItems().forEach(item->{ skuNos.add(item.getSkuNo()); });
			skuMap= productInfoCacheService.getSkuByCache2Map(skuNos);
		}
		
		String sn=ProductSnUtils.getComeSN();
		List<ComeOrderItem> comeOrderItems=new ArrayList<ComeOrderItem>();
		for(ProductStockComeSaveParamItem item: params.getItems()) {
			ComeOrderItem orderItem=new ComeOrderItem();
			orderItem.setComeSn(sn);
			orderItem.setProductNo(item.getProductNo());
			orderItem.setSkuNo(item.getSkuNo());
			orderItem.setQuantity(item.getQuantity());
			if(!CollectionUtils.isEmpty(skuMap)) {
				Sku sku= skuMap.get(item.getSkuNo());
				if(sku!=null) {
					orderItem.setName(sku.getName());
					orderItem.setThumbnail(sku.getImage());
					orderItem.setSpecifications(sku.getSpecificationValues());
				}
			}
			comeOrderItems.add(orderItem);
			psmsoparams.addItem(new ProductStockMultipleSkuItem(item.getSkuNo(), item.getQuantity()));
		}
		
		ComeOrder order=new ComeOrder();
		order.setSn(sn);
		order.setCompanyNo(userInfo.getCompanyNo());
		order.setCustNo(custNo);
		order.setCustName(params.getCustName());
		order.setWarehouseName(params.getWarehouseName());
		order.setCarNo(params.getCarNo());
		order.setOrderDate(LocalDateTime.now());
		order.setMemo(params.getMemo());
		order.addCreateParam(userInfo);
		order.addEditParam(userInfo);
		comeOrderService.save(order);
		comeOrderItemService.saveBatch(comeOrderItems);
		
		psmsoparams.setBusiType(ProductStockBusiTypeEnum.PSBT_Come.getCode());
		psmsoparams.setOpUserInfo(userInfo);
		this.opStockNotTransactional(psmsoparams);
	}
	
	@Override
	public void saveOutOrder(ProductStockOutSaveParam params ,LoginSimpleUserInfo userInfo) {
		ValidatorUtils.validate(params);
		String custNo;
		if(StringUtils.isEmpty(params.getCustNo())) {
			custNo=userInfo.getCompanyNo();
		}else {
			custNo=params.getCustNo();
		}
		
		String sn=ProductSnUtils.getOutSN();
		ProductStockMultipleSkuOpParam psmsoparams=new ProductStockMultipleSkuOpParam();
		psmsoparams.setCompanyNo(userInfo.getCompanyNo());
		psmsoparams.setCustNo(custNo);
		
		Map<String, Sku> skuMap=null;
		if(!CollectionUtils.isEmpty(params.getItems())) {
			Set<String> skuNos=new HashSet<String>();
			params.getItems().forEach(item->{ skuNos.add(item.getSkuNo()); });
			skuMap= productInfoCacheService.getSkuByCache2Map(skuNos);
		}
		
		List<OutOrderItem> orderItems=new ArrayList<OutOrderItem>();
		for(ProductStockOutSaveParamItem item:params.getItems()) {
			OutOrderItem orderItem=new OutOrderItem();
			orderItem.setOutSn(sn);
			orderItem.setProductNo(item.getProductNo());
			orderItem.setSkuNo(item.getSkuNo());
			orderItem.setQuantity(item.getQuantity());
			if(!CollectionUtils.isEmpty(skuMap)) {
				Sku sku= skuMap.get(item.getSkuNo());
				if(sku!=null) {
					orderItem.setName(sku.getName());
					orderItem.setThumbnail(sku.getImage());
					orderItem.setSpecifications(sku.getSpecificationValues());
				}
			}
			orderItems.add(orderItem);
			psmsoparams.addItem(new ProductStockMultipleSkuItem(item.getSkuNo(), item.getQuantity()));
		}
		
		OutOrder order=new OutOrder();
		order.setSn(sn);
		order.setCompanyNo(userInfo.getCompanyNo());
		order.setWarehouseName(params.getWarehouseName());
		order.setCustNo(custNo);
		order.setCustName(params.getCustName());
		order.setCarNo(params.getCarNo());
		order.setOrderDate(LocalDateTime.now());
		order.setMemo(params.getMemo());
		order.addCreateParam(userInfo);
		order.addEditParam(userInfo);
		psmsoparams.setBusiType(ProductStockBusiTypeEnum.PSBT_Out.getCode());
		psmsoparams.setOpUserInfo(userInfo);
		this.opStockNotTransactional(psmsoparams);
		outOrderService.save(order);
		outOrderItemService.saveBatch(orderItems);
		
	}
	
	@Transactional
	@Override
	public void opStock(ProductStockSingleSkuOpParam params) {
		ValidatorUtils.validate(params);
		this.opStockNotTransactional(params);
	}
	
	@Override
	public void opStockNotTransactional(ProductStockSingleSkuOpParam params) {
		ValidatorUtils.validate(params);
		if(StringUtils.isEmpty(params.getCustNo())) {
			params.setCustNo(params.getCompanyNo());
		}
		String lockKey=  ProductLockKeys.getProductStockSkuLockKey(params.getSkuNo());
		RLock lock = redissonClient.getLock(lockKey);
		try {
			boolean res = lock.tryLock(20, 10, TimeUnit.SECONDS);
			if (res) {
				try {
					this.opSingle(params, params.getOpUserInfo());
				} finally {
					lock.unlock();
				}
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
			throw new BusinessException(ProductErrorEnum.Stock_Lock);
		}
	}


	@Override
	public void opStockNotTransactional(ProductStockMultipleSkuOpParam params) {
		ValidatorUtils.validate(params);
		if(StringUtils.isEmpty(params.getCustNo())) {
			params.setCustNo(params.getCompanyNo());
		}
		List<RLock> locks=new ArrayList<RLock>();
		params.getItems().forEach(item->{
			String lockKey=  ProductLockKeys.getProductStockSkuLockKey(item.getSkuNo());
			 RLock lock = redissonClient.getLock(lockKey);
			 locks.add(lock);
		});
		RLock[] rlocks= {};
		RedissonMultiLock lock=new RedissonMultiLock(locks.toArray(rlocks));
//		RedissonRedLock lock = new RedissonRedLock(locks.toArray(rlocks));
		try {
			boolean res = lock.tryLock(40, 20, TimeUnit.SECONDS);
			if (res) {
				try {
					this.opMultiple(params, params.getOpUserInfo());
				} finally {
					lock.unlock();
				}
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
			throw new BusinessException(ProductErrorEnum.Stock_Lock);
		}
	}

	@Transactional
	@Override
	public void opStock(ProductStockMultipleSkuOpParam params) {
		this.opStockNotTransactional(params);
	}
	
	
	private void opMultiple(ProductStockMultipleSkuOpParam params, LoginSimpleUserInfo userInfo) {
		Set<String> skuNos=new HashSet<String>();
		params.getItems().forEach(item->{skuNos.add(item.getSkuNo());});
		Map<String, Sku> skuMap= productInfoCacheService.getSkuByCache2Map(skuNos);
		Map<String, ProductStockInfo> productStockMap= new HashMap<String, ProductStockInfo>();
		List<ProductStockInfo> productStockList= productStockInfoService.list(new QueryWrapper<ProductStockInfo>()
				.in("sku_no", skuNos)
				.eq(StringUtils.hasText(params.getCompanyNo()),"company_no", params.getCompanyNo())
				.eq(StringUtils.hasText(params.getCustNo()),"cust_no", params.getCustNo())
				);
		if(!CollectionUtils.isEmpty(productStockList)) {
			productStockList.forEach(item->{ productStockMap.put(item.getSkuNo(), item); });
		}
		
		List<ProductStockInfo> saveProductStocks=new ArrayList<ProductStockInfo>();
		List<ProductStockInfo> updateProductStocks=new ArrayList<ProductStockInfo>();
		
		for(ProductStockMultipleSkuItem psmsItem: params.getItems()) {
			Sku sku= skuMap.get(psmsItem.getSkuNo());
			if(sku==null) {
				throw new BusinessException(ProductErrorEnum.SkuInfo_NULL);
			}
			ProductStockInfo stockInfo= productStockMap.get(psmsItem.getSkuNo());
			if(stockInfo==null) {
				stockInfo=new ProductStockInfo();
				stockInfo.setCompanyNo(params.getCompanyNo());
				stockInfo.setCustNo(params.getCustNo());
				stockInfo.setProductNo(sku.getProductNo());
				stockInfo.setSkuNo(sku.getSkuNo());
				stockInfo.setNum(BigDecimal.ZERO);
				stockInfo.setFreezeNum(BigDecimal.ZERO);
				stockInfo.addCreateParam(userInfo);
				stockInfo.addEditParam(userInfo);
			}
			ProductStockBusiTypeEnum busiType =EnumUtils.getBaseCodeEnum(ProductStockBusiTypeEnum.class, params.getBusiType());
			if(busiType==null) {
				throw new BusinessException(ProductErrorEnum.Stock_BusiType_NULL); 
			}
			this.process(busiType, sku, stockInfo, psmsItem.getNum(), userInfo);
			if(stockInfo.getId()==null) {
				saveProductStocks.add(stockInfo);
			}else {
				updateProductStocks.add(stockInfo);
			}
		}
		
		if(saveProductStocks.size()>0) {
			productStockInfoService.saveBatch(saveProductStocks);
		}
		if(updateProductStocks.size()>0) {
			productStockInfoService.updateBatchById(updateProductStocks);
		}
	}
	
	private void opSingle(ProductStockSingleSkuOpParam params, LoginSimpleUserInfo userInfo) {
		Sku sku= productInfoCacheService.getSkuByCache(params.getSkuNo());
		if(sku==null) {
			throw new BusinessException(ProductErrorEnum.SkuInfo_NULL);
		}
		ProductStockInfo stockInfo= productStockInfoService.getOne(
				new QueryWrapper<ProductStockInfo>()
				.eq("sku_no", params.getSkuNo())
				.eq(StringUtils.hasText(params.getCompanyNo()),"company_no", params.getCompanyNo())
				.eq(StringUtils.hasText(params.getCustNo()),"cust_no", params.getCustNo())
				);
		if(stockInfo==null) {
			stockInfo=new ProductStockInfo();
			stockInfo.setCompanyNo(params.getCompanyNo());
			stockInfo.setCustNo(params.getCustNo());
			stockInfo.setProductNo(sku.getProductNo());
			stockInfo.setSkuNo(sku.getSkuNo());
			stockInfo.setNum(BigDecimal.ZERO);
			stockInfo.setFreezeNum(BigDecimal.ZERO);
			stockInfo.addCreateParam(userInfo);
		}
		
		ProductStockBusiTypeEnum busiType =EnumUtils.getBaseCodeEnum(ProductStockBusiTypeEnum.class, params.getBusiType());
		if(busiType==null) {
			throw new BusinessException(ProductErrorEnum.Stock_BusiType_NULL); 
		}
		this.process(busiType, sku, stockInfo, params.getNum(), userInfo);
		if(stockInfo.getId()==null) {
			productStockInfoService.save(stockInfo);
		}else {
			productStockInfoService.updateById(stockInfo);
		}
	}
	
	private void process(ProductStockBusiTypeEnum busiType,Sku sku,ProductStockInfo stockInfo, BigDecimal num, LoginSimpleUserInfo userInfo) {
		switch (busiType) {
			case PSBT_Come:
				stockInfo.setNum(stockInfo.getNum().add(num));
				stockInfo.addEditParam(userInfo);
				break;
			case PSBT_Out:
				if(stockInfo.getNum().doubleValue()<num.doubleValue()) {
					throw new BusinessException(ProductErrorEnum.Stock_Num_Less);
				}
				stockInfo.setNum(stockInfo.getNum().subtract(num));
				stockInfo.addEditParam(userInfo);
				break;
			case PSBT_Out_Freeze:
				if(stockInfo.getNum().doubleValue()<num.doubleValue()) {
					throw new BusinessException(ProductErrorEnum.Stock_Num_Less);
				}
				stockInfo.setNum(stockInfo.getNum().subtract(num));
				stockInfo.setFreezeNum(stockInfo.getFreezeNum().add(num));
				stockInfo.addEditParam(userInfo);
				break;
			case PSBT_Out_Defreeze:
				if(stockInfo.getFreezeNum().doubleValue()<num.doubleValue()) {
					throw new BusinessException(ProductErrorEnum.Stock_Num_Less);
				}
				stockInfo.setNum(stockInfo.getNum().add(num));
				stockInfo.setFreezeNum(stockInfo.getFreezeNum().subtract(num));
				stockInfo.addEditParam(userInfo);
				break;
			case PSBT_Out_Freeze_Sub:
				if(stockInfo.getFreezeNum().doubleValue()<num.doubleValue()) {
					throw new BusinessException(ProductErrorEnum.Stock_Num_Less);
				}
				stockInfo.setFreezeNum(stockInfo.getFreezeNum().subtract(num));
				stockInfo.addEditParam(userInfo);
				break;
		}
	}



	

	
	
	
}
